home *** CD-ROM | disk | FTP | other *** search
/ Symantec Visual Cafe for Java 2.5 / symantec-visual-cafe-2.5-database-dev-edition.iso / Visual Cafe Pro v1.0 / SOURCE.BIN / TreeView.java < prev    next >
Encoding:
Java Source  |  1997-06-19  |  37.3 KB  |  1,427 lines

  1. package symantec.itools.awt;
  2.  
  3.  
  4. import java.awt.BorderLayout;
  5. import java.awt.Color;
  6. import java.awt.Dimension;
  7. import java.awt.Event;
  8. import java.awt.FontMetrics;
  9. import java.awt.Font;
  10. import java.awt.Graphics;
  11. import java.awt.Image;
  12. import java.awt.LayoutManager;
  13. import java.awt.Scrollbar;
  14. import java.awt.Panel;
  15. import java.awt.Rectangle;
  16. import java.util.Vector;
  17.  
  18. //    01/15/97    RKM    Changed drawTree to make certain g1 has a font, before calling getFontMetrics on it
  19. //    01/15/07    RKM    Added invalidate to setTreeStructure
  20. //    01/29/97    TWB    Integrated changes from Windows and RKM's changes
  21. //     01/29/97    TWB    Integrated changes from Macintosh
  22. //  02/05/97    MSH Changed so that draws from first visible node
  23. //  02/27/97    MSH Merged change to add SEL_CHANGED
  24. //  04/02/97    TNM Draw all vertical lines
  25. //  04/14/97    RKM Changed bogus invalidates to repaint
  26. //                RKM    Changed hard coded sbVWidth to use preferredSize.width
  27. //                RKM Changed getTreeStructure so it actually returned a representation of what was in the treeview
  28. //                RKM    Rearranged a lot of stuff to get this to work
  29. //                RKM Changed g1.drawRect in drawTree to not overlap the scrollbar
  30. //                RKM Changed parseTreeStructure to not force a root node
  31. //  05/02/97    RKM Add arg to addSibling so caller could control whether the sible was added as the last sibling or not
  32. //                RKM    Changed insert to call addSibling with false when handling NEXT
  33. //                RKM    Kept addSibling with two params for compatibility
  34.  
  35. public class TreeView
  36.     extends Panel
  37. {
  38.     // constants for insertion
  39.     /**
  40.      * Insertion constant. Indicates that the new node is to be a child
  41.      * of the existing node.
  42.      */
  43.     public static final int CHILD   = 0;
  44.     /**
  45.      * Insertion constant. Indicates that the new node is to be the next
  46.      * sibling of the existing node.
  47.      */
  48.     public static final int NEXT    = CHILD + 1;
  49.     /**
  50.      * Insertion constant. Indicates that the new node is to be the last
  51.      * sibling of the existing node.
  52.      */
  53.     public static final int LAST    = CHILD + 2;
  54.     
  55.     /**
  56.      * Event type ID indicating the selection has changed.
  57.      */
  58.     public static final int SEL_CHANGED = 1006; //selection changed event
  59.     
  60.     private TreeNode rootNode;          // root node of tree
  61.     private TreeNode selectedNode;      // highlighted node
  62.     private TreeNode topVisibleNode;    // first node in window
  63.     
  64.     Scrollbar   sbV;            // vertical scrollbar
  65.     int            sbVPosition=0;    // hold value of vertical scrollbar
  66.     int            sbVWidth;       // width of vertical scrollbar
  67.     long        sbVTimer = -1;    // time of last vert scrollbar event
  68.     private int count=0;    // Number of nodes in the tree
  69.     private int viewCount=0;// Number of viewable nodes in the tree
  70.                             // (A node is viewable if all of its
  71.                             // parents are expanded.)
  72.     private Color bgHighlightColor = Color.blue;    // selection bg color
  73.     private Color fgHighlightColor = Color.white;   // selection fg color
  74.     private int viewHeight = 300;
  75.     private int viewWidth  = 300; // pixel size of tree display
  76.     private int viewWidest = 0 ;  // widest item displayable (for horz scroll)
  77.     
  78.     int cellSize    = 16;   // size of node image
  79.     int clickSize   = 8;    // size of mouse toggle (plus or minus)
  80.     int imageInset  = 3;    // left margin of node image
  81.     int textInset   = 6;    // left margin for text
  82.     int textBaseLine= 3;    // position of font baseline from bottom of cell
  83.     private FontMetrics fm; // current font metrics
  84.     long timeMouseDown;     // save time of last mouse down (for double click)
  85.     int doubleClickResolution = 333; // double-click speed in milliseconds
  86.     
  87.     /**
  88.      * Offscreen Image used for buffering the painting process.
  89.      */
  90.     protected Image im1;    // offscreen image
  91.     /**
  92.      * Offscreen graphics context used for buffering the painting process.
  93.      */
  94.     protected Graphics g1 = null;  // offscreen graphics context
  95.     
  96.     // constructors
  97.     
  98.     /**
  99.      * Constructs an empty default TreeView.
  100.      */
  101.     public TreeView()
  102.     {
  103.         super.setLayout(new BorderLayout());
  104.         add("East",  sbV = new Scrollbar(Scrollbar.VERTICAL));
  105.     }
  106.     
  107.     /**
  108.      * Constructs a TreeView with the given node.
  109.      */
  110.     public TreeView(TreeNode head)
  111.     {
  112.         this();
  113.         selectedNode = rootNode = head;
  114.         count=1;
  115.     }
  116.     
  117.     /**
  118.      * Sets this component's background color.
  119.      * This is a standard Java AWT method which gets called to change
  120.      * the background color of this component.
  121.      *
  122.      * @param c the new background color
  123.      * @see #getBackground
  124.      * @see #setForeground
  125.      */
  126.     public void setBackground(Color c)
  127.     {
  128.         super.setBackground(c);
  129.         repaint();
  130.     }
  131.     
  132.     /**
  133.      * Determines the current background color.
  134.      * This is a standard Java AWT method which gets called to determine the
  135.      * current background color for this component. If the background color
  136.      * hasn't been explicity specified using the setBackground() method, the
  137.      * background color of this component's parent is returned.
  138.      *
  139.      * @return the current background color
  140.      * @see #setBackground
  141.      * @see #getForeground
  142.      */
  143.     public Color getBackground()
  144.     {
  145.         return super.getBackground();
  146.     }
  147.     
  148.     /**
  149.      * Sets this component's foreground color.
  150.      * This is a standard Java AWT method which gets called to change
  151.      * the foreground color of this component.
  152.      *
  153.      * @param c the new foreground color
  154.      * @see #getForeground
  155.      * @see #setBackground
  156.      */
  157.     public void setForeground(Color c)
  158.     {
  159.         super.setForeground(c);
  160.         repaint();
  161.     }
  162.     
  163.     /**
  164.      * Gets the current foreground color.
  165.      * This is a standard Java AWT method which gets called to determine
  166.      * the current foreground color for this component. If the foreground
  167.      * color has not been explicitly set using the setForeground() method,
  168.      * the foreground color of of this component's parent is returned.
  169.      *
  170.      * @return the current foreground color
  171.      * @see #setForeground
  172.      * @see #getBackground
  173.      */
  174.     public Color getForeground()
  175.     {
  176.         return super.getForeground();
  177.     }
  178.     
  179.     /**
  180.      * Sets the foreground selection hilite color.
  181.      * @param c the new foreground selection hilite color
  182.      * @see #getFgHilite
  183.      */
  184.     public void setFgHilite(Color c)
  185.     {
  186.         fgHighlightColor = c;
  187.         repaint();
  188.     }
  189.     
  190.     /**
  191.      * Gets the current foreground selection hilite color.
  192.      * @return the current foreground selection hilite color
  193.      * @see #setFgHilite
  194.      */
  195.     public Color getFgHilite()
  196.     {
  197.         return fgHighlightColor;
  198.     }
  199.     
  200.     /**
  201.      * Gets the current background selection hilite color.
  202.      * @return the current background selection hilite color
  203.      * @see #getBgHilite
  204.      */
  205.     public void setBgHilite(Color c)
  206.     {
  207.         bgHighlightColor = c;
  208.         repaint();
  209.     }
  210.     
  211.     /**
  212.      * Gets the current background selection hilite color.
  213.      * @return the current background selection hilite color
  214.      * @see #setBgHilite
  215.      */
  216.     public Color getBgHilite()
  217.     {
  218.         return bgHighlightColor;
  219.     }
  220.     
  221.     // Insert a new node relative to a node in the tree.
  222.     // position = CHILD inserts the new node as a child of the node
  223.     // position = NEXT inserts the new node as the next sibling
  224.     // position = LAST inserts the new node as the last sibling
  225.     /**
  226.      * Inserts a new node relative to an existing node in the tree.
  227.      * @param newNode the new node to insert into the tree
  228.      * @param relativeNode the existing node used for a position reference
  229.      * @param position where to insert the new node relative to relativeNode.
  230.      * Legal values are CHILD, NEXT and LAST.
  231.      * @see #CHILD
  232.      * @see #NEXT
  233.      * @see #LAST
  234.      * @see #append
  235.     */
  236.     public void insert(TreeNode newNode, TreeNode relativeNode, int position)
  237.     {
  238.         if (newNode==null || relativeNode==null)
  239.             return;
  240.         
  241.         if (exists(relativeNode)==false)
  242.             return;
  243.         
  244.         switch (position)
  245.         {
  246.             case CHILD:
  247.                 addChild(newNode, relativeNode);
  248.                 break;
  249.             
  250.             case NEXT:
  251.                 addSibling(newNode, relativeNode, false);
  252.                 break;
  253.             
  254.             case LAST:
  255.                 addSibling(newNode, relativeNode, true);
  256.                 break;
  257.     
  258.             default:
  259.                 // invalid position
  260.                 return;
  261.         }
  262.     }
  263.     
  264.     /**
  265.      * Returns the "root" node. The root node is the top-most node
  266.      * in the tree hierarchy. All other nodes stem from that one.
  267.      * @return the root tree node
  268.      */
  269.     public TreeNode getRootNode()
  270.     {
  271.         return rootNode;
  272.     }
  273.     
  274.     /**
  275.      * Returns the total number of nodes in the tree.
  276.      */
  277.     public int getCount()
  278.     {
  279.         return count;
  280.     }
  281.     
  282.     /**
  283.      * Returns the total number of viewable nodes in the tree.
  284.      * A node is viewable if all of its parents are expanded.
  285.      */
  286.     public int getViewCount()
  287.     {
  288.         return viewCount;
  289.     }
  290.     
  291.     /**
  292.      * Determines if the given node is viewable.
  293.      * A node is viewable if all of its parents are expanded.
  294.      * @param node the node to check
  295.      * @return true if the node is visible, false if it is not
  296.      * @see #viewable(java.lang.String)
  297.      */
  298.     boolean viewable(TreeNode node)
  299.     {
  300.         for (int i=0; i<viewCount; i++)
  301.         {
  302.             if (node == v.elementAt(i))
  303.             {
  304.                 return true;
  305.             }
  306.         }
  307.     
  308.         return false;
  309.     }
  310.     
  311.     /**
  312.      * Determines if the node with the given text is viewable.
  313.      * A node is viewable if all of its parents are expanded.
  314.      * @param s the node text to find
  315.      * @return true if the node is visible, false if it is not
  316.      * @see #viewable(TreeNode)
  317.      */
  318.     boolean viewable(String s)
  319.     {
  320.         if (s==null)
  321.         {
  322.             return false;
  323.         }
  324.     
  325.         for (int i=0; i<viewCount; i++)
  326.         {
  327.             TreeNode tn = (TreeNode)v.elementAt(i);
  328.     
  329.             if (tn.text != null)
  330.             {
  331.                 if (s.equals(tn.text))
  332.                 {
  333.                     return true;
  334.                 }
  335.             }
  336.         }
  337.     
  338.         return false;
  339.     }
  340.     
  341.     /**
  342.      * Determines if the given node is in the TreeView.
  343.      * @param node the node to check
  344.      * @return true if the node is in the TreeView, false if it is not
  345.      * @see #exists(java.lang.String)
  346.      */
  347.     public boolean exists(TreeNode node)
  348.     {
  349.         recount();
  350.     
  351.         for (int i=0; i<count; i++)
  352.         {
  353.             if (node == e.elementAt(i))
  354.             {
  355.                 return true;
  356.             }
  357.         }
  358.     
  359.         return false;
  360.     }
  361.     
  362.     /**
  363.      * Determines if the node with the given text is in the TreeView.
  364.      * @param s the node text to find
  365.      * @return true if the node is in the TreeView, false if it is not
  366.      * @see #exists(TreeNode)
  367.      */
  368.     public boolean exists(String s)
  369.     {
  370.         recount();
  371.     
  372.         if (s==null)
  373.         {
  374.             return false;
  375.         }
  376.     
  377.         for (int i=0; i<count; i++)
  378.         {
  379.             TreeNode tn = (TreeNode)e.elementAt(i);
  380.     
  381.             if (tn.text != null)
  382.             {
  383.                 if (s.equals(tn.text))
  384.                 {
  385.                     return true;
  386.                 }
  387.             }
  388.         }
  389.     
  390.         return false;
  391.     }
  392.     
  393.     // add new node to level 0
  394.     /**
  395.      * Adds a new node at root level. If there is no root node, the given
  396.      * node is made the root node. If there is a root node, the given node
  397.      * is made a sibling of the root node.
  398.      * @param newNode the new node to add
  399.      * @see #insert
  400.      */
  401.     public void append(TreeNode newNode)
  402.     {
  403.         if (rootNode==null)
  404.         {
  405.             rootNode=newNode;
  406.             selectedNode = rootNode;
  407.             count=1;
  408.         }
  409.         else
  410.         {
  411.             addSibling(newNode, rootNode, true);
  412.         }
  413.     }
  414.     
  415.     void addChild(TreeNode newNode, TreeNode relativeNode)
  416.     {
  417.         if (relativeNode.child == null)
  418.         {
  419.             relativeNode.child = newNode;
  420.             newNode.parent = relativeNode;
  421.             count++;
  422.         }
  423.         else
  424.         {
  425.             addSibling(newNode, relativeNode.child, true);
  426.         }
  427.     
  428.         relativeNode.numberOfChildren++;
  429.     }
  430.     
  431.     void addSibling(TreeNode newNode, TreeNode siblingNode)
  432.     {
  433.         addSibling(newNode,siblingNode,true);
  434.     }
  435.     
  436.     void addSibling(TreeNode newNode, TreeNode siblingNode, boolean asLastSibling)
  437.     {
  438.         if (asLastSibling)
  439.         {
  440.             //Find last sibling
  441.             TreeNode tempNode = siblingNode;
  442.             while (tempNode.sibling != null)
  443.                 tempNode = tempNode.sibling;
  444.             
  445.             tempNode.sibling = newNode;
  446.         }
  447.         else
  448.         {
  449.             //Insert the newNode below the siblingNode
  450.             newNode.sibling = siblingNode.sibling;
  451.             
  452.             siblingNode.sibling = newNode;
  453.         }
  454.         
  455.         //Set the parent of the new node to the parent of the sibling
  456.         newNode.parent = siblingNode.parent;
  457.         
  458.         count++;
  459.     }
  460.     
  461.     /**
  462.      * Removes the node with the given text from the TreeView.
  463.      * @param s the node text to find
  464.      * @return the TreeNode removed from this TreeView or null if not found
  465.      * @see #remove(TreeNode)
  466.      * @see #removeSelected
  467.      */
  468.     public TreeNode remove(String s)
  469.     {
  470.         recount();
  471.         
  472.         for (int i=0; i<count; i++)
  473.         {
  474.             TreeNode tn = (TreeNode)e.elementAt(i);
  475.         
  476.             if (tn.text != null)
  477.             {
  478.                 if (s.equals(tn.text))
  479.                 {
  480.                     remove(tn);
  481.                     return tn;
  482.                 }
  483.             }
  484.         }
  485.         
  486.         return null;
  487.     }
  488.     
  489.     /**
  490.      * Removes the currently selected node from the TreeView.
  491.      * @see #remove(TreeNode)
  492.      * @see #remove(java.lang.String)
  493.      */
  494.     public void removeSelected()
  495.     {
  496.         if (selectedNode != null)
  497.         {
  498.             remove(selectedNode);
  499.         }
  500.     }
  501.     
  502.     /**
  503.      * Removes the given node from the TreeView.
  504.      * @param node the node to remove
  505.      * @return the TreeNode removed from this TreeView or null if not found
  506.      * @see #remove(java.lang.String)
  507.      * @see #removeSelected
  508.      */
  509.     public void remove(TreeNode node)
  510.     {
  511.         if (!exists(node))
  512.         {
  513.             return;
  514.         }
  515.     
  516.         if (node == selectedNode)
  517.         {
  518.             int index = v.indexOf(selectedNode);
  519.     
  520.             if (index == -1)
  521.             {    //not viewable
  522.                 index = e.indexOf(selectedNode);
  523.             }
  524.     
  525.             if (index > viewCount-1)
  526.             {
  527.                 index = viewCount-1;
  528.             }
  529.     
  530.             if (index>0)
  531.             {
  532.                 changeSelection((TreeNode)v.elementAt(index-1));
  533.             }
  534.             else if (viewCount>0)
  535.             {
  536.                 changeSelection((TreeNode)v.elementAt(1));
  537.             }
  538.         }
  539.     
  540.         // remove node and its decendents
  541.         if (node.parent != null)
  542.         {
  543.             if (node.parent.child == node)
  544.             {
  545.                 if (node.sibling != null)
  546.                 {
  547.                     node.parent.child = node.sibling;
  548.                 }
  549.                 else
  550.                 {
  551.                     node.parent.child = null;
  552.                     node.parent.collapse();
  553.                 }
  554.             }
  555.             else
  556.             {
  557.                 TreeNode tn=node.parent.child;
  558.     
  559.                 while (tn.sibling != node)
  560.                 {
  561.                     tn = tn.sibling;
  562.                 }
  563.     
  564.                 if (node.sibling != null)
  565.                 {
  566.                     tn.sibling = node.sibling;
  567.                 }
  568.                 else
  569.                 {
  570.                     tn.sibling = null;
  571.                 }
  572.             }
  573.         }
  574.         else
  575.         {
  576.             if (node == rootNode)
  577.             {
  578.                 if (node.sibling == null)
  579.                 {
  580.                     rootNode=null;
  581.                 }
  582.                 else
  583.                 {
  584.                     rootNode=node.sibling;
  585.                 }
  586.             }
  587.             else
  588.             {
  589.                 TreeNode tn = rootNode;
  590.     
  591.                 while (tn.sibling != node)
  592.                 {
  593.                     tn = tn.sibling;
  594.                 }
  595.     
  596.                 if (node.sibling != null)
  597.                 {
  598.                     tn.sibling = node.sibling;
  599.                 }
  600.                 else
  601.                 {
  602.                     tn.sibling = null;
  603.                 }
  604.             }
  605.         }
  606.     
  607.         recount();
  608.     }
  609.     
  610.     // print each node of TreeView beginning with node
  611.     /**
  612.      * Print out the text of each node in the TreeView beginning with
  613.      * the given node.
  614.      * @param node the first node to print
  615.      */
  616.     public void printTree(TreeNode node)
  617.     {
  618.         if (node==null)
  619.         {
  620.             return;
  621.         }
  622.     
  623.         System.out.println(node.text);
  624.         printTree(node.child);
  625.         printTree(node.sibling);
  626.     }
  627.     
  628.     private Vector e; // e is vector of existing nodes
  629.     private void recount()
  630.     {
  631.         count = 0;
  632.         e = new Vector();
  633.     
  634.         if (rootNode != null)
  635.         {
  636.             rootNode.depth=0;
  637.             traverse(rootNode);
  638.         }
  639.     }
  640.     
  641.     private void traverse(TreeNode node)
  642.     {
  643.         count++;
  644.         e.addElement(node);
  645.     
  646.         if (node.child != null)
  647.         {
  648.             node.child.depth = node.depth+1;
  649.             traverse(node.child);
  650.         }
  651.         if (node.sibling != null)
  652.         {
  653.             node.sibling.depth = node.depth;
  654.             traverse(node.sibling);
  655.         }
  656.     }
  657.     
  658.     private Vector v; // v is vector of viewable nodes
  659.     private void resetVector()
  660.     {
  661.         // Traverses tree to put nodes into vector v
  662.         // for internal processing. Depths of nodes are set,
  663.         // and viewCount and viewWidest is set.
  664.         v = new Vector(count);
  665.         viewWidest=30;
  666.         
  667.         if (count < 1)
  668.         {
  669.             viewCount = 0;
  670.             return;
  671.         }
  672.         
  673.         rootNode.depth=0;
  674.         vectorize(rootNode,true,v);
  675.         viewCount = v.size();
  676.     }
  677.     
  678.     private void vectorize
  679.             (TreeNode    node,
  680.              boolean    respectExpanded,
  681.              Vector        nodeVector)
  682.     {
  683.         if (node == null)
  684.             return;
  685.         
  686.         nodeVector.addElement(node);
  687.         
  688.         if ((!respectExpanded && node.child != null) || node.isExpanded())
  689.         {
  690.             node.child.depth = node.depth + 1;
  691.             vectorize(node.child,respectExpanded,nodeVector);
  692.         }
  693.         
  694.         if (node.sibling != null)
  695.         {
  696.             node.sibling.depth = node.depth;
  697.             vectorize(node.sibling,respectExpanded,nodeVector);
  698.         }
  699.     }
  700.     
  701.     private void debugVector()
  702.     {
  703.         int vSize = v.size();
  704.     
  705.         for (int i=0; i<count; i++)
  706.         {
  707.             TreeNode node = (TreeNode) v.elementAt(i);
  708.             System.out.println(node.text);
  709.         }
  710.     }
  711.     
  712.     // -----------------------------------------
  713.     // --------- event related methods ---------
  714.     // -----------------------------------------
  715.     
  716.     /**
  717.      * Processes events for this component.
  718.      * This is a standard Java AWT method which gets called by the AWT
  719.      * to handle this component's events. The default handler for
  720.      * components dispatches to one of the following methods as needed:
  721.      * action(), gotFocus(), lostFocus(), keyDown(), keyUp(), mouseEnter(),
  722.      * mouseExit(), mouseMove(), mouseDrag(), mouseDown(), or mouseUp().
  723.      *
  724.      * @param event the event to handle
  725.      * @return true if the event was handled and no further action is needed,
  726.      * false to pass the event to this component's parent
  727.      * @see java.awt.Component#action
  728.      * @see java.awt.Component#gotFocus
  729.      * @see java.awt.Component#lostFocus
  730.      * @see #keyDown
  731.      * @see java.awt.Component#keyUp
  732.      * @see java.awt.Component#mouseEnter
  733.      * @see java.awt.Component#mouseExit
  734.      * @see java.awt.Component#mouseMove
  735.      * @see java.awt.Component#mouseDrag
  736.      * @see #mouseDown
  737.      * @see java.awt.Component#mouseUp
  738.      */
  739.     public boolean handleEvent(Event event)
  740.     {
  741.         if (event.target == sbV)
  742.         {
  743.             if (event.arg == null)
  744.             {
  745.                 return false;
  746.             }
  747.     
  748.             if (sbVPosition != ((Integer) event.arg).intValue())
  749.             {
  750.                 sbVPosition = ((Integer) event.arg).intValue();
  751.                 redraw();
  752.             }
  753.         }
  754.     
  755.         return(super.handleEvent(event));
  756.     }
  757.     
  758.     /**
  759.      * Processes MOUSE_DOWN events.
  760.      * This is a standard Java AWT method which gets called by the AWT
  761.      * method handleEvent() in response to receiving a MOUSE_DOWN
  762.      * event. These events occur when the mouse button is pressed while
  763.      * inside this component.
  764.      *
  765.      * @param event the event
  766.      * @param x the component-relative horizontal coordinate of the mouse
  767.      * @param y the component-relative vertical coordinate of the mouse
  768.      *
  769.      * @return true if the event was handled
  770.      *
  771.      * @see java.awt.Component#mouseUp
  772.      * @see #handleEvent
  773.      */
  774.     public boolean mouseDown(Event event, int x, int y)
  775.     {
  776.         int index = (y/cellSize) + sbVPosition;
  777.     
  778.         if (index > viewCount-1)
  779.         {
  780.             return false; //clicked below the last node
  781.         }
  782.     
  783.         TreeNode oldNode = selectedNode;
  784.         TreeNode newNode = (TreeNode)v.elementAt(index);
  785.         int newDepth = newNode.getDepth();
  786.     
  787.         changeSelection(newNode);
  788.         // check for toggle box click
  789.         // todo: make it a bit bigger
  790.         Rectangle toggleBox = new Rectangle(cellSize*newDepth + cellSize/4,
  791.                                             (index-sbVPosition)*cellSize + clickSize/2,
  792.                                             clickSize, clickSize);
  793.         if (toggleBox.inside(x,y))
  794.         {
  795.             newNode.toggle();
  796.             sendActionEvent(event);
  797.             redraw();
  798.         }
  799.         else
  800.         {
  801.             // check for double click
  802.             long currentTime = event.when;
  803.     
  804.             if ((newNode==oldNode) && ((event.when-timeMouseDown)<doubleClickResolution))
  805.             {
  806.                 newNode.toggle();
  807.                 redraw();
  808.                 sendActionEvent(event);
  809.                 return false;
  810.             }
  811.             else
  812.             {
  813.                 //single click action could be added here
  814.                 timeMouseDown = event.when;
  815.             }
  816.     
  817.         }
  818.     
  819.         return true;
  820.     }
  821.     
  822.     /**
  823.      * Processes KEY_PRESS and KEY_ACTION events.
  824.      * This is a standard Java AWT method which gets called by the AWT
  825.      * method handleEvent() in response to receiving a KEY_PRESS or
  826.      * KEY_ACTION event. These events occur when this component has the focus
  827.      * and the user presses a "normal" or an "action" (F1, page up, etc) key.
  828.      *
  829.      * @param event the Event
  830.      * @param key the key that was pressed
  831.      * @return true if the event was handled
  832.      * @see java.awt.Component#keyUp
  833.      * @see #handleEvent
  834.      */
  835.     public boolean keyDown(Event event, int key)
  836.     {
  837.         int index = v.indexOf(selectedNode);
  838.     
  839.         switch (key)
  840.         {
  841.             case 10:    //enter key
  842.                 sendActionEvent(event);
  843.                 requestFocus();
  844.                 break;
  845.             case Event.LEFT:    //left arrow
  846.                 if (selectedNode.isExpanded())
  847.                 {
  848.                     selectedNode.toggle();
  849.                     redraw();
  850.                     break;
  851.                 }
  852.                 // else drop through to "UP" with no "break;"
  853.             case Event.UP:
  854.                 if (index > 0)
  855.                 {
  856.                     index--;
  857.                     changeSelection((TreeNode)v.elementAt(index));
  858.                     requestFocus();
  859.                 }
  860.                 break;
  861.             case Event.RIGHT:
  862.                 if (selectedNode.isExpandable() && (!selectedNode.isExpanded()))
  863.                 {
  864.                     selectedNode.toggle();
  865.                     sendActionEvent(event);
  866.                     redraw();
  867.                     break;
  868.                 }
  869.     
  870.                 if (!selectedNode.isExpandable())
  871.                 {
  872.                     break;
  873.                 }
  874.                 // else drop thru' to DOWN
  875.             case Event.DOWN:
  876.                 if (index < viewCount-1)
  877.                 {
  878.                     index++;
  879.                     changeSelection((TreeNode)v.elementAt(index));
  880.                     requestFocus();
  881.                 }
  882.                 break;
  883.         }
  884.     
  885.         return false;
  886.     }
  887.     
  888.     private void sendActionEvent(Event event)
  889.     {
  890.         int id = event.id;
  891.         Object arg = event.arg;
  892.         event.id = Event.ACTION_EVENT;
  893.         event.arg = new String(selectedNode.getText());
  894.         postEvent(event);
  895.         event.id = id;
  896.         event.arg = arg;
  897.     }
  898.     
  899.     /**
  900.      * Returns the currently selected node.
  901.      */
  902.     public TreeNode getSelectedNode()
  903.     {
  904.         return selectedNode;
  905.     }
  906.     
  907.     /**
  908.      * Gets the text of the currently selected node.
  909.      * @return the text of the currently selected node or null if no node
  910.      * is selected
  911.      */
  912.     public String getSelectedText()
  913.     {
  914.         if (selectedNode==null)
  915.         {
  916.             return null;
  917.         }
  918.     
  919.         return selectedNode.getText();
  920.     }
  921.     
  922.     private void changeSelection(TreeNode node)
  923.     {
  924.         if (node == selectedNode)
  925.         {
  926.             return;
  927.         }
  928.     
  929.         TreeNode oldNode = selectedNode;
  930.         selectedNode = node;
  931.         drawNodeText(oldNode, (v.indexOf(oldNode) - sbVPosition)*cellSize, true);
  932.         drawNodeText(node,    (v.indexOf(node)    - sbVPosition)*cellSize, true);
  933.         // send select event
  934.     
  935.         int index = v.indexOf(selectedNode);
  936.     
  937.         postEvent(new Event(this, SEL_CHANGED, selectedNode));
  938.     
  939.         if (index < sbVPosition)
  940.         { //scroll up
  941.             sbVPosition--;
  942.             sbV.setValue(sbVPosition);
  943.             redraw();
  944.             return;
  945.         }
  946.     
  947.         if (index >= sbVPosition + (viewHeight-cellSize/2)/cellSize)
  948.         {
  949.             sbVPosition++;
  950.             sbV.setValue(sbVPosition);
  951.             redraw();
  952.             return;
  953.         }
  954.     
  955.         repaint();
  956.     }
  957.     
  958.     // -----------------------------------------
  959.     // --------- graphics related methods ------
  960.     // -----------------------------------------
  961.     /**
  962.      * Handles redrawing of this component on the screen.
  963.      * This is a standard Java AWT method which gets called by the Java
  964.      * AWT (repaint()) to handle repainting this component on the screen.
  965.      * The graphics context clipping region is set to the bounding rectangle
  966.      * of this component and its <0,0> coordinate is this component's
  967.      * top-left corner.
  968.      * Typically this method paints the background color to clear the
  969.      * component's drawing space, sets graphics context to be the foreground
  970.      * color, and then calls paint() to draw the component.
  971.      *
  972.      * It is overridden here to reduce flicker by eliminating the uneeded 
  973.      * clearing of the background.
  974.      *
  975.      * @param g the graphics context
  976.      * @see java.awt.Component#repaint
  977.      * @see #paint
  978.      */
  979.     public void update (Graphics g)
  980.     {
  981.         //(eliminates background draw to reduce flicker)
  982.         paint(g);
  983.     }
  984.     
  985.     /**
  986.      * Paints this component using the given graphics context.
  987.      * This is a standard Java AWT method which typically gets called
  988.      * by the AWT to handle painting this component. It paints this component
  989.      * using the given graphics context. The graphics context clipping region
  990.      * is set to the bounding rectangle of this component and its <0,0>
  991.      * coordinate is this component's top-left corner.
  992.      *
  993.      * @param g the graphics context used for painting
  994.      * @see java.awt.Component#repaint
  995.      * @see #update
  996.      */
  997.     public void paint (Graphics g)
  998.     {
  999.         Dimension d = size();
  1000.         
  1001.         if ((d.width != viewWidth) || (d.height != viewHeight))
  1002.         {
  1003.             // size has changed, must redraw image
  1004.             redraw();
  1005.             //return;
  1006.         }
  1007.         else if (symantec.beans.Beans.isDesignTime())
  1008.         {
  1009.             resetVector();
  1010.             layout();
  1011.             drawTree();
  1012.         }
  1013.         
  1014.         g.drawImage(im1, 0, 0, this);
  1015.     }
  1016.     
  1017.     /**
  1018.      * Lays out the vertical scrollbar as needed, then draws the TreeView into 
  1019.      an offscreen image. This is used for cleaner refresh.
  1020.      */
  1021.     public void redraw()
  1022.     {
  1023.         resetVector();
  1024.     
  1025.         if (viewCount > viewHeight/cellSize)
  1026.         {
  1027.             // need the vertical scrollbar
  1028.             sbV.setValues(sbVPosition, (viewHeight/cellSize), 0, viewCount-2);
  1029.             sbV.setPageIncrement(1);
  1030.             sbVWidth = sbV.preferredSize().width;
  1031.             getParent().paintAll(getParent().getGraphics());
  1032.             sbV.show();
  1033.             layout();
  1034.         }
  1035.         else
  1036.         {
  1037.             sbV.hide();
  1038.             sbVWidth = 1;
  1039.             sbVPosition = 0;
  1040.             layout();
  1041.         }
  1042.     
  1043.         drawTree();
  1044.         repaint();
  1045.     }
  1046.     
  1047.     /**
  1048.      * Draws the TreeView into an offscreen image. This is used for cleaner refresh.
  1049.      */
  1050.     public void drawTree()
  1051.     {
  1052.         Dimension d = size();
  1053.     
  1054.         if ((d.width != viewWidth) || (d.height != viewHeight) || (g1==null))
  1055.         {
  1056.             // size has changed, must resize image
  1057.             im1 = createImage(d.width, d.height);
  1058.             if (g1 != null) {
  1059.                 g1.dispose();
  1060.             }
  1061.             g1 = im1.getGraphics();
  1062.             viewWidth=d.width;
  1063.             viewHeight=d.height;
  1064.         }
  1065.     
  1066.         Font f = getFont();  //unix version might not provide a default font
  1067.     
  1068.         //Make certain there is a font
  1069.         if (f == null)
  1070.         {
  1071.             f = new Font("TimesRoman", Font.PLAIN, 13);
  1072.             g1.setFont(f);
  1073.             setFont(f);
  1074.         }
  1075.     
  1076.         //Make certain the graphics object has a font (Mac doesn't seem to)
  1077.         if (f != null)
  1078.         {
  1079.             if (g1.getFont() == null)
  1080.                 g1.setFont(f);
  1081.         }
  1082.         
  1083.         fm = g1.getFontMetrics();
  1084.         g1.setColor(getBackground());
  1085.         g1.fillRect(0,0,viewWidth,viewHeight);  // clear image
  1086.         
  1087.         //do drawing for each visible node
  1088.         int lastOne=sbVPosition+viewHeight/cellSize+1;
  1089.         
  1090.         if (lastOne > viewCount)
  1091.         {
  1092.             lastOne = viewCount;
  1093.         }
  1094.         
  1095.         TreeNode outerNode = (TreeNode)v.elementAt(sbVPosition);
  1096.         for (int i=sbVPosition; i<lastOne; i++)
  1097.         {
  1098.             TreeNode node=(TreeNode)v.elementAt(i);
  1099.             int x = cellSize*(node.depth + 1);
  1100.             int y = (i-sbVPosition)*cellSize;
  1101.     
  1102.             // draw lines
  1103.             g1.setColor(getForeground());
  1104.     
  1105.             // draw vertical sibling line
  1106.             if (node.sibling != null)
  1107.             {
  1108.                 int k = v.indexOf(node.sibling) - i;
  1109.     
  1110.                 if (k > lastOne)
  1111.                 {
  1112.                     k = lastOne;
  1113.                 }
  1114.     
  1115.                 drawDotLine(x - cellSize/2, y + cellSize/2,
  1116.                             x - cellSize/2, y + cellSize/2 +  k*cellSize);
  1117.     
  1118.             }
  1119.             // if sibling is above page, draw up to top of page for this level
  1120.             for (int m=0; m<i; m++)
  1121.             {
  1122.                 TreeNode sib = (TreeNode)v.elementAt(m);
  1123.     
  1124.                 if ((sib.sibling == node) && (m<sbVPosition))
  1125.                 {
  1126.                     drawDotLine (x - cellSize/2, 0,
  1127.                                  x - cellSize/2, y + cellSize/2);
  1128.                 }
  1129.             }
  1130.     
  1131.             // draw vertical child lines
  1132.             if (node.isExpanded())
  1133.             {
  1134.                 drawDotLine(x + cellSize/2, y + cellSize -2 ,
  1135.                             x + cellSize/2, y + cellSize + cellSize/2);
  1136.             }
  1137.             // draw node horizontal line
  1138.             g1.setColor(getForeground());
  1139.             drawDotLine(x - cellSize/2, y + cellSize/2,
  1140.                         x + cellSize/2, y + cellSize/2);
  1141.     
  1142.             // draw toggle box
  1143.             if (node.isExpandable())
  1144.             {
  1145.                 g1.setColor(getBackground());
  1146.                 g1.fillRect(cellSize*(node.depth) + cellSize/4, y + clickSize/2, clickSize, clickSize );
  1147.                 g1.setColor(getForeground());
  1148.                 g1.drawRect(cellSize*(node.depth) + cellSize/4, y + clickSize/2, clickSize, clickSize );
  1149.                 // cross hair
  1150.                 g1.drawLine(cellSize*(node.depth) + cellSize/4 +2,             y + cellSize/2,
  1151.                             cellSize*(node.depth) + cellSize/4 + clickSize -2, y + cellSize/2);
  1152.     
  1153.                 if (!(node.isExpanded()))
  1154.                 {
  1155.                     g1.drawLine(cellSize*(node.depth) + cellSize/2, y + clickSize/2 +2,
  1156.                                 cellSize*(node.depth) + cellSize/2, y + clickSize/2 + clickSize -2);
  1157.                 }
  1158.             }
  1159.     
  1160.             // draw node image
  1161.             Image nodeImage = node.getImage();
  1162.     
  1163.             if (nodeImage != null)
  1164.             {
  1165.                 g1.drawImage(nodeImage, x+imageInset, y, this);
  1166.             }
  1167.     
  1168.             // draw node text
  1169.             if (node.text != null)
  1170.             {
  1171.                 drawNodeText(node, y, node==selectedNode);
  1172.             }
  1173.     
  1174.             if(outerNode.depth>node.depth)
  1175.                 outerNode = node;
  1176.         }
  1177.     
  1178.         // draw outer vertical lines
  1179.         while((outerNode = outerNode.parent) != null)
  1180.         {
  1181.             if(outerNode.sibling != null)
  1182.                 drawDotLine (cellSize*(outerNode.depth + 1) - cellSize/2, 0,
  1183.                              cellSize*(outerNode.depth + 1) - cellSize/2, d.height);;
  1184.         }
  1185.         
  1186.         // draw border
  1187.         g1.setColor(getForeground());
  1188.         g1.drawRect(0,0,viewWidth - sbVWidth, viewHeight - 1);
  1189.     }
  1190.     
  1191.     private void drawNodeText(TreeNode node, int yPosition, boolean eraseBackground)
  1192.     {
  1193.         Color fg, bg;
  1194.         int depth=node.depth;
  1195.         Image nodeImage = node.getImage();
  1196.         int textOffset = ((depth + 1) * (cellSize)) + cellSize + textInset - (nodeImage==null ? 12:0);
  1197.     
  1198.         if (node==selectedNode)
  1199.         {
  1200.             fg=fgHighlightColor;
  1201.             bg=bgHighlightColor;
  1202.         }
  1203.         else
  1204.         {
  1205.             fg = getForeground();
  1206.             bg = getBackground();
  1207.         }
  1208.     
  1209.         if (eraseBackground)
  1210.         {
  1211.             g1.setColor(bg);
  1212.             g1.fillRect(textOffset-1, yPosition+1, fm.stringWidth(node.text)+4, cellSize-1);
  1213.         }
  1214.     
  1215.         g1.setColor(fg);
  1216.         g1.drawString(node.text, textOffset, yPosition + cellSize - textBaseLine);
  1217.     }
  1218.     
  1219.     private void drawDotLine(int x0, int y0, int x1, int y1)
  1220.     {
  1221.        if (y0==y1)
  1222.        {
  1223.             for (int i = x0; i<x1; i+=2)
  1224.             {
  1225.                g1.drawLine(i,y0, i, y1);
  1226.             }
  1227.         }
  1228.         else
  1229.         {
  1230.             for (int i = y0; i<y1; i+=2)
  1231.             {
  1232.                 g1.drawLine(x0, i, x1, i);
  1233.             }
  1234.         }
  1235.     }
  1236.     
  1237.     /**
  1238.      * Initializes the TreeView from a String array. 
  1239.      * There is one string for each node in the array. That string 
  1240.      * contains the text of the node indented with same number of
  1241.      * leading spaces as the depth of that node in the tree.
  1242.      * @param s the String array used for initialization
  1243.      * @see #getTreeStructure
  1244.      */
  1245.     public void setTreeStructure(String s[])
  1246.     {
  1247.         rootNode = selectedNode = null;
  1248.         
  1249.         try
  1250.         {
  1251.             parseTreeStructure(s);
  1252.         }
  1253.         catch(InvalidTreeNodeException e)
  1254.         {
  1255.             System.out.println(e);
  1256.         }
  1257.         
  1258.         invalidate();
  1259.     }
  1260.     
  1261.     /**
  1262.      * Gets a String array that reflects the current TreeView's contents.
  1263.      * There is one string for each node in the array. That string 
  1264.      * contains the text of the node indented with same number of
  1265.      * leading spaces as the depth of that node in the tree.
  1266.      * @return the String array that reflects the TreeView's contents
  1267.      * @see #setTreeStructure
  1268.      */
  1269.     public String[] getTreeStructure()
  1270.     {
  1271.         //Create a vector representing current tree structure
  1272.         Vector nodesVector = new Vector(count);
  1273.         rootNode.depth = 0;
  1274.         vectorize(rootNode,false,nodesVector);
  1275.         
  1276.         //Convert this to a String[]
  1277.         int numNodes = nodesVector.size();
  1278.         String[] treeStructure = new String[numNodes];
  1279.         for (int i = 0;i < numNodes;i++)
  1280.         {
  1281.             TreeNode thisNode = (TreeNode)nodesVector.elementAt(i);
  1282.             
  1283.             //Add appropriate number of blanks
  1284.             String treeString = "";
  1285.             for (int numBlanks = 0;numBlanks < thisNode.depth;numBlanks++)
  1286.                 treeString += ' ';
  1287.             
  1288.             //Add tree
  1289.             treeString += thisNode.text;
  1290.             
  1291.             //Put string into array
  1292.             treeStructure[i] = treeString;
  1293.         }
  1294.         
  1295.         return treeStructure;
  1296.     }
  1297.     
  1298.     private void parseTreeStructure(String tempStructure[])
  1299.         throws InvalidTreeNodeException
  1300.     {
  1301.         for(int i = 0; i < tempStructure.length; i++)
  1302.         {
  1303.             String entry = tempStructure[i];
  1304.             int indentLevel = findLastPreSpace(entry)/*+1*/;
  1305.             
  1306.             if (indentLevel == -1)
  1307.                 throw new InvalidTreeNodeException();
  1308.             
  1309.             if (rootNode == null)
  1310.             {
  1311.                 if (indentLevel != 0)
  1312.                     throw new InvalidTreeNodeException();
  1313.                 
  1314.                 TreeNode node = new TreeNode(entry.trim());
  1315.                 node.setDepth(indentLevel);
  1316.                 append(node);
  1317.             }
  1318.             else
  1319.             {
  1320.                 TreeNode currentNode = rootNode;
  1321.                 
  1322.                 for(int j = 1; j < indentLevel; j++)
  1323.                 {
  1324.                     int numberOfChildren = currentNode.numberOfChildren;
  1325.                     TreeNode tempNode = null;
  1326.                     
  1327.                     if (numberOfChildren > 0)
  1328.                     {
  1329.                         tempNode = currentNode.child;
  1330.                         
  1331.                         while(tempNode.sibling != null)
  1332.                             tempNode = tempNode.sibling;
  1333.                     }
  1334.                     
  1335.                     if(tempNode != null)
  1336.                         currentNode = tempNode;
  1337.                     else
  1338.                         break;
  1339.                 }
  1340.                 
  1341.                 int diff = indentLevel - currentNode.getDepth();
  1342.                 
  1343.                 if (diff > 1)
  1344.                     throw new InvalidTreeNodeException();
  1345.                 
  1346.                 TreeNode node = new TreeNode(entry.trim());
  1347.                 node.setDepth(indentLevel);
  1348.                 
  1349.                 if (diff == 1)
  1350.                     insert(node, currentNode, CHILD);
  1351.                 else
  1352.                     insert(node, currentNode, NEXT);
  1353.             }
  1354.         }
  1355.     }
  1356.     
  1357.     private int findLastPreSpace(String s)
  1358.     {
  1359.         int length;
  1360.     
  1361.         length = s.length();
  1362.         
  1363.         if(s.charAt(0) != ' ' && s.charAt(0) != '\t')
  1364.         {
  1365.             return 0;
  1366.         }
  1367.     
  1368.         for(int i = 1; i < length; i++)
  1369.         {
  1370.             if(s.charAt(i) != ' ' && s.charAt(i) != '\t')
  1371.             {
  1372.                 return i;
  1373.             }
  1374.         }
  1375.     
  1376.         return -1;
  1377.     }
  1378.     
  1379.     /**
  1380.      * Returns the recommended dimensions to properly display this component.
  1381.      * This is a standard Java AWT method which gets called to determine
  1382.      * the recommended size of this component.
  1383.      *
  1384.      * @see #minimumSize
  1385.      */
  1386.     public synchronized Dimension preferredSize()
  1387.     {
  1388.         //???RKM??? This is bogus, shouldn't this have something to do with the data ???
  1389.         return new Dimension(175, 125);
  1390.     }
  1391.     
  1392.     /**
  1393.      * Returns the minimum dimensions to properly display this component.
  1394.      * This is a standard Java AWT method which gets called to determine
  1395.      * the minimum size of this component.
  1396.      *
  1397.      * @see #preferredSize
  1398.      */
  1399.     public synchronized Dimension minimumSize()
  1400.     {
  1401.         return new Dimension(50, 50);
  1402.     }
  1403.     
  1404.     /**
  1405.      * Takes no action.
  1406.      * This is a standard Java AWT method which gets called to specify
  1407.      * which layout manager should be used to layout the components in
  1408.      * standard containers.
  1409.      *
  1410.      * Since layout managers CANNOT BE USED with this container the standard
  1411.      * setLayout has been OVERRIDDEN for this container and does nothing.
  1412.      *
  1413.      * @param lm the layout manager to use to layout this container's components
  1414.      * (IGNORED)
  1415.      * @see java.awt.Container#getLayout
  1416.      **/
  1417.     public void setLayout(LayoutManager lm)
  1418.     {
  1419.     }
  1420. }
  1421.  
  1422.  
  1423. class InvalidTreeNodeException
  1424.     extends Exception
  1425. {
  1426. }
  1427.